﻿using System;
using System.Collections.Generic;
using System.Text;
using PostSharp.Extensibility.Tasks;
using PostSharp.CodeModel;
using System.Reflection;
using PostSharp.Collections;

namespace B4ALL.JITL
{

    /// <summary>
    /// PostSharp taks. Injects custom equality and inequality operators in JITLObjects
    /// </summary>
    public class JITLOperatorTask : PostSharp.Extensibility.Task
    {
        private IMethod EqualityMethod;

        protected override void Initialize()
        {
            EqualityMethod = this.Project.Module.FindMethod(
                typeof(B4ALL.JITL.JITLObject).GetMethod("Equality",
                new Type[] { typeof(Object), typeof(Object) }),
                BindingOptions.Default);
        }

        public override bool Execute()
        {
            CustomAttributeDictionaryTask customAttributeDictionary = CustomAttributeDictionaryTask.GetTask(this.Project);

            IEnumerator<ICustomAttributeInstance> customAttributeEnumerator = customAttributeDictionary.GetCustomAttributesEnumerator(typeof(JITLObjectAttribute), true);

            while (customAttributeEnumerator.MoveNext())
            {
                TypeDefDeclaration typeDef = customAttributeEnumerator.Current.TargetElement
                                                 as TypeDefDeclaration;

                AddEqualityOp(typeDef);
                AddInequalityOp(typeDef);
            }

            return true;
        }

        private void AddEqualityOp(TypeDefDeclaration typeDef)
        {
            MethodDefDeclaration defDecla = new MethodDefDeclaration();
            defDecla.Name = "op_Equality";
            defDecla.Attributes |= MethodAttributes.Static;
            defDecla.Attributes |= MethodAttributes.Public;
            defDecla.Attributes |= MethodAttributes.HideBySig;
            defDecla.Attributes |= MethodAttributes.SpecialName;

            typeDef.Methods.Add(defDecla);

            SetParameters(defDecla, typeDef);

            MethodBodyDeclaration methodBody = new MethodBodyDeclaration();
            defDecla.MethodBody = methodBody;

            InstructionBlock block = defDecla.MethodBody.CreateInstructionBlock();
            defDecla.MethodBody.RootInstructionBlock = block;

            InstructionSequence sequence = defDecla.MethodBody.CreateInstructionSequence();


            InstructionWriter iw = new InstructionWriter();

            block.AddInstructionSequence(sequence, NodePosition.Before, null);

            iw.AttachInstructionSequence(sequence);

            LocalVariableSymbol boolLocal = block.DefineLocalVariable(Project.Module.Cache.GetIntrinsic(IntrinsicType.Boolean), "retvalue");

            //iw.EmitInstruction(OpCodeNumber.Nop);

            //iw.EmitInstructionString(OpCodeNumber.Ldstr, "Equality Operator Called");
            //iw.EmitInstructionMethod(OpCodeNumber.Call, WriteLineMethod);


            //iw.EmitInstruction(OpCodeNumber.Nop);

            iw.EmitInstruction(OpCodeNumber.Ldarg_0);
            //iw.EmitInstructionField(OpCodeNumber.Ldfld, ComparisonField);

            iw.EmitInstruction(OpCodeNumber.Ldarg_1);
            //iw.EmitInstructionField(OpCodeNumber.Ldfld, ComparisonField);

            //iw.EmitInstruction(OpCodeNumber.Ceq);

            iw.EmitInstructionMethod(OpCodeNumber.Call, EqualityMethod);

            iw.EmitInstruction(OpCodeNumber.Stloc_0);

            InstructionSequence sequence2 = defDecla.MethodBody.CreateInstructionSequence();

            block.AddInstructionSequence(sequence2, NodePosition.After, null);

            iw.EmitBranchingInstruction(OpCodeNumber.Br_S, sequence2);

            iw.DetachInstructionSequence();

            iw.AttachInstructionSequence(sequence2);

            iw.EmitInstruction(OpCodeNumber.Ldloc_0);

            iw.EmitInstruction(OpCodeNumber.Ret);

            iw.DetachInstructionSequence();
        }

        private void AddInequalityOp(TypeDefDeclaration typeDef)
        {
            MethodDefDeclaration defDecla = new MethodDefDeclaration();
            defDecla.Name = "op_Inequality";
            defDecla.Attributes |= MethodAttributes.Static;
            defDecla.Attributes |= MethodAttributes.Public;
            defDecla.Attributes |= MethodAttributes.HideBySig;
            defDecla.Attributes |= MethodAttributes.SpecialName;

            typeDef.Methods.Add(defDecla);

            SetParameters(defDecla, typeDef);

            MethodBodyDeclaration methodBody = new MethodBodyDeclaration();
            defDecla.MethodBody = methodBody;

            InstructionBlock block = defDecla.MethodBody.CreateInstructionBlock();
            defDecla.MethodBody.RootInstructionBlock = block;

            InstructionSequence entrySequence = defDecla.MethodBody.CreateInstructionSequence();

            InstructionWriter iw = new InstructionWriter();

            InstructionSequence sequence = defDecla.MethodBody.CreateInstructionSequence();

            block.AddInstructionSequence(sequence, NodePosition.Before, null);

            iw.AttachInstructionSequence(sequence);

            LocalVariableSymbol boolLocal = block.DefineLocalVariable(Project.Module.Cache.GetIntrinsic(IntrinsicType.Boolean), "retvalue");

            //iw.EmitInstruction(OpCodeNumber.Nop);

            //iw.EmitInstructionString(OpCodeNumber.Ldstr, "Inequality Operator Called");
            //iw.EmitInstructionMethod(OpCodeNumber.Call, WriteLineMethod);


            //iw.EmitInstruction(OpCodeNumber.Nop);

            iw.EmitInstruction(OpCodeNumber.Ldarg_0);
            //iw.EmitInstructionField(OpCodeNumber.Ldfld, ComparisonField);

            iw.EmitInstruction(OpCodeNumber.Ldarg_1);
            //iw.EmitInstructionField(OpCodeNumber.Ldfld, ComparisonField);

            //iw.EmitInstruction(OpCodeNumber.Ceq);

            iw.EmitInstructionMethod(OpCodeNumber.Call, EqualityMethod);

            iw.EmitInstruction(OpCodeNumber.Ldc_I4_0);

            iw.EmitInstruction(OpCodeNumber.Ceq);


            iw.EmitInstruction(OpCodeNumber.Stloc_0);


            InstructionSequence sequence2 = defDecla.MethodBody.CreateInstructionSequence();

            block.AddInstructionSequence(sequence2, NodePosition.After, null);

            iw.EmitBranchingInstruction(OpCodeNumber.Br_S, sequence2);

            iw.DetachInstructionSequence();

            iw.AttachInstructionSequence(sequence2);


            iw.EmitInstruction(OpCodeNumber.Ldloc_0);


            iw.EmitInstruction(OpCodeNumber.Ret);

            iw.DetachInstructionSequence();
        }

        private void SetParameters(MethodDefDeclaration defDecla, TypeDefDeclaration typeDef)
        {
            ParameterDeclaration retParameter = new ParameterDeclaration();
            retParameter.ParameterType = Project.Module.Cache.GetIntrinsic(IntrinsicType.Boolean); //CodeModel.TypeSignatures.IntrinsicTypeSignature.GetInstance(IntrinsicType.Boolean, this.Project.Module);
            retParameter.Attributes = ParameterAttributes.Retval;
            defDecla.ReturnParameter = retParameter;

            ParameterDeclaration param1 = new ParameterDeclaration(0, "inst1", typeDef);
            ParameterDeclaration param2 = new ParameterDeclaration(1, "inst2", typeDef);

            defDecla.Parameters.Add(param1);
            defDecla.Parameters.Add(param2);
        }

    }
}
